package com.byx.chatclient.widget;

import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * StickyView
 *
 * @author Superb
 * @date 2021/4/22 22:37
 */
public class StickyView extends androidx.appcompat.widget.AppCompatTextView {
    /**
     * 最大拖拽范围
     */
    private float mFarthestDistance = 200;
    /**
     * 动画中固定员的最小半径
     */
    private float mMinFixRadius = 8;
    /**
     * 拖拽圆的圆心
     */
    PointF mDragCanterPoint = new PointF(250, 250);
    /**
     * 固定圆的圆心
     */
    PointF mFixCanterPoint = new PointF(250, 250);
    /**
     * 控制点
     */
    PointF mCanterPoint = new PointF(250, 250);

    /**
     * 固定圆的切点
     */
    PointF[] mFixTangentPointes = new PointF[]{new PointF(235, 250),
            new PointF(265, 250)};
    /**
     * 拖拽圆的切点
     */
    PointF[] mDragTangentPoint = new PointF[]{new PointF(230, 250),
            new PointF(270, 250)};
    /**
     * 拖拽圆半径
     */
    float mDragRadius = 20;
    /**
     * 固定圆半径
     */
    float mFixRadius = 15;
    /**
     * 超出范围
     */
    private boolean isOut;
    /**
     * 在超出范围的地方松手
     */
    private boolean isOutUp;
    private int mStatusBarHeight;
    private Paint mPaint;
    private Path mPath;
    private float rangeMove;

    public StickyView(Context context) {
        super(context);
        init();
    }

    public StickyView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public StickyView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
//      需要去除状态栏高度偏差
        canvas.translate(0, -mStatusBarHeight);
//      移出了范围后将不再绘制链接部分和固定圆
        if (!isOut) {
//            根据圆心距动态绘制固定圆大小
            float mFixRadius = updateStickRadius();
            canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFixRadius,
                    mPaint);
//      设置控制点，这里的控制点选择的是两圆心连接成的直线的中心位置
            mCanterPoint.set((mDragCanterPoint.x + mFixCanterPoint.x) / 2,
                    (mDragCanterPoint.y + mFixCanterPoint.y) / 2);
//            接下来是计算两个圆的外切点，会用到一点几何知识，忘了的回去找高中老师
            float dy = mDragCanterPoint.y - mFixCanterPoint.y;
            float dx = mDragCanterPoint.x - mFixCanterPoint.x;

            if (dx != 0) {
                float k1 = dy / dx;
                float k2 = -1 / k1;
                mDragTangentPoint = getIntersectionPoints(
                        mDragCanterPoint, mDragRadius, (double) k2);
                mFixTangentPointes = getIntersectionPoints(
                        mFixCanterPoint, mFixRadius, (double) k2);
            } else {
                mDragTangentPoint = getIntersectionPoints(
                        mDragCanterPoint, mDragRadius, (double) 0);
                mFixTangentPointes = getIntersectionPoints(
                        mFixCanterPoint, mFixRadius, (double) 0);
            }
//           必须重设上一次的路径
            mPath.reset();
//            moveTo顾名思义就是移动到某个位置，这里移动到固定圆的第一个外切点
            mPath.moveTo(mFixTangentPointes[0].x, mFixTangentPointes[0].y);
//           quadTo是绘制二阶贝塞尔曲线，这种曲线很想ps里面画矢量路径的那种。二阶的话需要一个控制点，一个起点一个终点
            mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
                    mDragTangentPoint[0].x, mDragTangentPoint[0].y);
//            从上一个点绘制一条直线到下面这个位置
            mPath.lineTo(mDragTangentPoint[1].x, mDragTangentPoint[1].y);
//            再绘制一条二阶贝塞尔曲线
            mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
                    mFixTangentPointes[1].x, mFixTangentPointes[1].y);
//            执行close，表示形成闭合路径
            mPath.close();
//            绘制到界面上
            canvas.drawPath(mPath, mPaint);

        }
//        当在范围外松手的时候是不再绘制拖拽圆的
        if (!isOutUp) {
            canvas.drawCircle(mDragCanterPoint.x, mDragCanterPoint.y,
                    mDragRadius, mPaint);
        }
//        参考范围，没实际作用
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFarthestDistance, mPaint);
        mPaint.setStyle(Paint.Style.FILL);

        canvas.restore();
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isOut = false;
                float startX = event.getRawX();
                float startY = event.getRawY();
                updateDragCenterPoint(startX, startY);
                break;
            case MotionEvent.ACTION_MOVE:
                float endX = event.getRawX();
                float endY = event.getRawY();
//                更加手的移动位置绘制拖拽圆的位置
                updateDragCenterPoint(endX, endY);
                distance();
//                当移出了规定的范围的时候
                if (rangeMove > mFarthestDistance) {
                    isOut = true;
                } else {
//                    不能把isOut改为false,因为移出一次后就算它移出过了
//				isOut=false;
                    isOutUp = false;
                }
                break;
            case MotionEvent.ACTION_UP:
//                防止误操作
                distance();
                if (isOut) {
                    outUp();
                }
                // 没有超出，做动画
                else {
                    inUp();
                }
                invalidate();
                break;
        }
        return true;
    }
    /**
     * 移动出规定范围
     */
    private void outUp() {
        // 外面松手
        if (rangeMove > mFarthestDistance) {
            isOutUp = true;
        }
        // 里面松手
        else {
            isOutUp = false;
        }
        updateDragCenterPoint(mFixCanterPoint.x, mFixCanterPoint.y);
    }
    /**
     * 计算此时拖拽圆心到固定圆心的距离
     */
    private void distance() {
        rangeMove = getDistanceBetween2Points(
                mFixCanterPoint, mDragCanterPoint);
    }

    /**
     * 移动的时候一直在范围内，最后在范围内松手
     */
    private void inUp() {
        final PointF startPoint = new PointF(mDragCanterPoint.x,
                mDragCanterPoint.y);
        final PointF endPoint = new PointF(mFixCanterPoint.x,
                mFixCanterPoint.y);
        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float fraction = animation.getAnimatedFraction();
                PointF byPercent = getPointByPercent(
                        startPoint, endPoint, fraction);
                updateDragCenterPoint(byPercent.x, byPercent.y);
            }
        });
        animator.setInterpolator(new OvershootInterpolator(4.0f));
        animator.setDuration(500);
        animator.start();
    }

    /**
     * 计算拖动过程中固定圆的半径
     */
    private float updateStickRadius() {
        float distance = (float) Math.sqrt(Math.pow(mDragCanterPoint.y - mFixCanterPoint.y, 2)
                + Math.pow(mDragCanterPoint.x - mFixCanterPoint.x, 2));
        distance = Math.min(distance, mFarthestDistance);
        float percent = distance * 1.0f / mFarthestDistance;
        return mFixRadius + (mMinFixRadius - mFixRadius) * percent;
    }

    /**
     * 更新拖拽圆圆心
     */
    private void updateDragCenterPoint(float x, float y) {
        mDragCanterPoint.set(x, y);
        invalidate();
    }

    /**
     * 获取状态栏高度
     *
     * @param v
     * @return
     */
    public static int getStatusBarHeight(View v) {
        if (v == null) {
            return 0;
        }
        Rect frame = new Rect();
        v.getWindowVisibleDisplayFrame(frame);
        return frame.top;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mStatusBarHeight = getStatusBarHeight(this);
    }
    /**
     * Get point between p1 and p2 by percent.
     * 根据百分比获取两点之间的某个点坐标
     * @param p1
     * @param p2
     * @param percent
     * @return
     */
    public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
        return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));
    }
    /**
     * 根据分度值，计算从start到end中，fraction位置的值。fraction范围为0 -> 1
     * @param fraction
     * @param start
     * @param end
     * @return
     */
    public static float evaluateValue(float fraction, Number start, Number end){
        return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
    }
    /**
     * As meaning of method name.
     * 获得两点之间的距离
     * @param p0
     * @param p1
     * @return
     */
    public static float getDistanceBetween2Points(PointF p0, PointF p1) {
        float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
        return distance;
    }
    /**
     * Get the point of intersection between circle and line.
     * 获取 通过指定圆心，斜率为lineK的直线与圆的交点。
     *
     * @param pMiddle The circle center point.
     * @param radius  The circle radius.
     * @param lineK   The slope of line which cross the pMiddle.
     * @return
     */
    public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
        PointF[] points = new PointF[2];

        float radian, xOffset = 0, yOffset = 0;
        if (lineK != null) {

            radian = (float) Math.atan(lineK);
            xOffset = (float) (Math.cos(radian) * radius);
            yOffset = (float) (Math.sin(radian) * radius);
        } else {
            xOffset = radius;
            yOffset = 0;
        }
        points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y + yOffset);
        points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y - yOffset);

        return points;
    }

}
